﻿using System;
using System.IO;
using System.Collections;
using System.Threading;
using System.Text.RegularExpressions;
using System.DirectoryServices;
using System.Net;
using System.Management;

using log4net;

using NetworkAssetManager; 
using NetworkAssetManager.General;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Text;

//TODO_NORM : Some mechanism to handle the exception in case of thread termination
namespace NetworkAssetManager.Network
{
     
    public enum SearchType
    {
        Domain, 
        Machines
    }
    public class NetworkHelper
    {
        private Thread mainThread;
        private static readonly ILog logger = LogManager.GetLogger(typeof(NetworkHelper));
        private List<NetworkAssetManager.Entity.EntCredential> _lstCred = null; 

        #region Delegates and events for NetworkHelper

        public delegate void DelegateMachineDiscoveryStart(Object sender, DiscoveryDetailsArgs args);
        public event DelegateMachineDiscoveryStart MachineDiscoveryStart;

        public delegate void DelegateMachineFound(Object sender, MachineDetailsArgs args);
        public event DelegateMachineFound MachineFound;

        public delegate void DelegateMachineDiscoveryComplete(Object sender, EventArgs args);
        public event DelegateMachineDiscoveryComplete MachineDiscoveryComplete;

        public delegate void DelegateDomainDiscoveryStart(Object sender, DiscoveryDetailsArgs args);
        public event DelegateDomainDiscoveryStart DomainDiscoveryStart;

        public delegate void DelegateDomainFound(Object sender, DomainDetailsArgs args);
        public event DelegateDomainFound DomainFound;

        public delegate void DelegateDomainDiscoveryComplete(Object sender, EventArgs args);
        public event DelegateDomainDiscoveryComplete DomainDiscoveryComplete;


        #endregion


        public List<NetworkAssetManager.Entity.EntCredential> Credentials
        {
            set
            {
                if ( _lstCred!=null)
                    _lstCred.Clear(); 
                
                _lstCred = value; 
            }
        }

        
        public void DiscoverMachines(SearchType type, string criteria )
        {
            logger.Info("Get machines with criteria "+criteria);
            MyParams objMyParam = new MyParams();
            objMyParam.criteria = criteria;
            objMyParam.type = type; 

            mainThread = new Thread(new ParameterizedThreadStart(SearchCriteria));
            mainThread.IsBackground = true;
            mainThread.Start(objMyParam);
        }

        private void SearchCriteria(object param)
        {
            MyParams objMyParam =(MyParams)param; 
            DirectoryEntry root = null;
            DirectoryEntries parent = null;

            if ( objMyParam.type == SearchType.Machines)
            {
                if (MachineDiscoveryStart != null)
                {
                    logger.Info("DiscoveryStart event raised");
                    MachineDiscoveryStart(this, null);
                }
                logger.Info("Starting the background thread for scanning machines");
            }
            else
            {
                if (DomainDiscoveryStart != null)
                {
                    DomainDiscoveryStart(this, null);
                }
            }

            try
            {
                using (root = new DirectoryEntry("WinNT:"))
                {
                    parent = root.Children;
                    foreach (DirectoryEntry complist in parent)
                    {
                        if (objMyParam.type == SearchType.Machines)
                        {
                            if (objMyParam.criteria.Equals(complist.Name, StringComparison.OrdinalIgnoreCase) || objMyParam.criteria == "*")
                                SearchComputers(complist.Name);
                        }
                        else
                        {
                            if (DomainFound != null)
                            {
                                DomainDetailsArgs objDomainArgs = new DomainDetailsArgs();
                                objDomainArgs.DomainName = complist.Name;
                                DomainFound(this, objDomainArgs);
                            }
                        }
                    }
                    parent = null;
                }
            }
            catch (Exception e)
            {
                logger.Info("Exception occured in machine retrival <"+e.Message+">");
            }
            finally
            {
                if (objMyParam.type == SearchType.Machines)
                {
                    logger.Info("Finally raise the DiscoveryComplete event for startCompleteScan");
                    if (MachineDiscoveryComplete != null)
                    {
                        MachineDiscoveryComplete(this, null);
                    }
                }
                else
                {
                    if (DomainDiscoveryComplete != null)
                    {
                        DomainDiscoveryComplete(this, null);
                    }
                }
            }
        }

        private void SearchComputers(string domainNames)
        {
            string query = "WinNT://" + domainNames;
            DirectoryEntry container = null;
            using (container = new DirectoryEntry(query))
            {
                DirectoryEntries computers = container.Children;
                computers.SchemaFilter.Add("Computer");
                foreach (DirectoryEntry computer in computers)
                {
                    try
                    {
                        logger.Debug("Machine found: " + computer.Name);
                        if (MachineFound != null)
                        {
                            string _message = string.Empty;
                            logger.Debug("Raising machine found event");
                            MachineDetailsArgs objMachineArgs = new MachineDetailsArgs();
                            objMachineArgs.DomainName = container.Name;
                            objMachineArgs.MachineName = computer.Name;
                            objMachineArgs.IPAddress = GetMachineIPAddress(computer.Name);
                            objMachineArgs.CredentialID = GetAssociatedCredential(computer.Name, ref _message);
                            if ( objMachineArgs.CredentialID == -1)
                            {
                                objMachineArgs.Discovered = false; 
                            }
                            else
                            {
                                objMachineArgs.Discovered = true;
                            }
                            objMachineArgs.StatusMessage = _message;
                            MachineFound(this, objMachineArgs);
                        }
                    }
                    catch (Exception)
                    {

                    }
                }
            }
        }

        public void GetMachinesByName(string machineName)
        {
            logger.Info("GetMachinesByName called");
            if (MachineDiscoveryStart != null)
            {
                MachineDiscoveryStart(this, null);
            }

            mainThread = new Thread(new ParameterizedThreadStart(AddMachineMyName));
            mainThread.IsBackground = true;
            mainThread.Start(machineName);
        }

        private void AddMachineMyName(object param)
        {
            string machineName = (string)param;
            logger.Debug("Machine found: " + machineName);
            if (MachineFound != null)
            {
                string _message = string.Empty;
                logger.Debug("Raising machine found event");
                MachineDetailsArgs objMachineArgs = new MachineDetailsArgs();
                objMachineArgs.DomainName = "";
                objMachineArgs.MachineName = machineName;
                objMachineArgs.IPAddress = GetMachineIPAddress(machineName);
                objMachineArgs.CredentialID = GetAssociatedCredential(machineName, ref _message);
                objMachineArgs.StatusMessage = _message;
                MachineFound(this, objMachineArgs);

                if (MachineDiscoveryComplete != null)
                {
                    MachineDiscoveryComplete(this, null);
                }
            }
        }


//TODO_HIGH: Add some check to identify the IP in the current network range. Problem in case of VM adapters
        private string GetMachineIPAddress(string machineName)
        {
            string ipaddress = "";
            try
            {
                IPAddress[] addresslist = Dns.GetHostAddresses(machineName);

//HACK: (2009/07/01) Getting just the first found IP address. This could be a VM ware address 

                if ( addresslist != null)
                {
                    ipaddress = addresslist[0].ToString(); 
                }
/*
                foreach (IPAddress theaddress in addresslist)
                {
                    ipaddress += theaddress.ToString();
                    ipaddress += ",";
                }
                char[] MyChar = { ',' };
                ipaddress = ipaddress.TrimEnd(MyChar);
*/
            }
            catch (Exception)
            {
                ipaddress = "";
            }
            return ipaddress;
        }

        private int GetAssociatedCredential(string hostName, ref string _message)
        {
            ManagementScope _WmiScope = null;
            int credID = -1; 
            if (checkPing(hostName) == true)
            {
                foreach (NetworkAssetManager.Entity.EntCredential cred in _lstCred )
                {
                    ConnectionOptions options = new ConnectionOptions();
                    options.Username = cred.Username;
                    options.Password = cred.Password;
                    try
                    {
                        _WmiScope = new ManagementScope("\\\\" + hostName + "\\root\\cimv2", options);
                        _WmiScope.Connect();
                        credID = cred.CredentialID; 
                        _message = "Access Ok"; 
                        break; 
                    }
                    catch (ManagementException e)
                    {
                        if (e.ErrorCode == ManagementStatus.LocalCredentials)
                        {
                            // this is a hack
                            // this is local machine so retry by null Username and Password
                            options.Password = null;
                            options.Username = null;
                            credID = 0;
                            _message = "Local machine"; 
                            break; 
                        }
                        else if (ManagementStatus.AccessDenied == e.ErrorCode)
                        {
                            _message = "Access denied";
                            credID = -1; 
                        }
                    }
                    catch (Exception e)
                    {
                        _message = e.Message;
                        credID = -1; 
                    }
                }
            }
            else
            {
                _message = "Machine is not accessible";
                credID = -1;  
            }

            return credID; 
        }

        private bool checkPing(string _hostName)
        {
            bool bStatus = false;
            Ping pingSender = new Ping();
            PingOptions options = new PingOptions();

            // Use the default Ttl value which is 128,
            // but change the fragmentation behavior.
            options.DontFragment = true;

            // Create a buffer of 32 bytes of data to be transmitted.
            string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
            byte[] buffer = Encoding.ASCII.GetBytes(data);
            int timeout = 10;
            PingReply reply = null;
            try
            {
                reply = pingSender.Send(_hostName, timeout, buffer, options);
            }
            catch (System.Exception)
            {
                bStatus = false;
            }

            if (reply != null && reply.Status == IPStatus.Success)
                bStatus = true;

            return bStatus;
        }

    }
    class MyParams
    {
        public string criteria;
        public SearchType type; 
    }
}

